package com.atguigu.gmall.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.list.model.*;
import com.atguigu.gmall.product.client.ProductFeignClient;
import com.atguigu.gmall.product.model.BaseAttrInfo;
import com.atguigu.gmall.product.model.BaseCategoryView;
import com.atguigu.gmall.product.model.BaseTrademark;
import com.atguigu.gmall.product.model.SkuInfo;
import com.atguigu.gmall.service.GoodsRepository;
import com.atguigu.gmall.service.SearchService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

@Service
@Slf4j
@SuppressWarnings("all")
public class SearchServiceImpl implements SearchService {
    @Autowired
    private ProductFeignClient productFeignClient;
    @Autowired
    private RestHighLevelClient restHighLevelClient;
    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;
    @Autowired
    private GoodsRepository goodsRepository;
    @Autowired
    private RedisTemplate redisTemplate;

    //声明常量
    public static final  String INDEX_NAME = "goods";


    /**
     * 处理上架请求(商品文件录入索引库)
     * @param skuId
     * @return
     */
    @Override
    public void upperGoods(Long skuId) {
        //1.创建索引库对象
        Goods goods = new Goods();
        goods.setId(skuId);

        //2.远程调用商品微服务,根据商品skuId查询商品基本信息
        CompletableFuture<SkuInfo> skuInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
            if (skuInfo != null) {
                goods.setCategory3Id(skuInfo.getCategory3Id()); //商品id
                goods.setDefaultImg(skuInfo.getSkuDefaultImg()); //商品默认图片
                goods.setTitle(skuInfo.getSkuName()); //商品标题
                goods.setTmId(skuInfo.getTmId()); //获取商标id
            }
            return skuInfo;
        }, threadPoolExecutor);

        BigDecimal skuPrice = productFeignClient.getSkuPrice(skuId);
        if(skuPrice != null){
            goods.setPrice(skuPrice.doubleValue());
        }

        //3.远程调用分类微服务,根据分类id查询商品分类信息
        CompletableFuture<Void> categoryCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(skuInfo -> {
            BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
            if(categoryView != null){
                goods.setCategory1Id(categoryView.getCategory1Id());
                goods.setCategory1Name(categoryView.getCategory1Name());

                goods.setCategory2Id(categoryView.getCategory2Id());
                goods.setCategory2Name(categoryView.getCategory2Name());

                goods.setCategory3Id(categoryView.getCategory3Id());
                goods.setCategory3Name(categoryView.getCategory3Name());
            }
        }, threadPoolExecutor);

        //4.远程调用商品微服务,根据品牌id查询品牌信息
        CompletableFuture<Void> priceCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(skuInfo -> {
        BaseTrademark trademark = productFeignClient.getTrademarkById(skuInfo.getTmId());
        if(trademark != null){
            goods.setTmId(trademark.getId());
            goods.setTmName(trademark.getTmName());
            goods.setTmLogoUrl(trademark.getLogoUrl());
        }
        }, threadPoolExecutor);

        //5.远程调用商品微服务,根据skuId查询平台属性列表
        CompletableFuture<Void> attrInfoCompletableFuture = CompletableFuture.runAsync(() -> {
        List<BaseAttrInfo> attrList = productFeignClient.getAttrList(skuId);
        if(!CollectionUtils.isEmpty(attrList)){
            List<SearchAttr> searchAttrList = attrList.stream().map(baseAttrInfo -> {
                SearchAttr searchAttr = new SearchAttr();
                searchAttr.setAttrId(baseAttrInfo.getId());
                searchAttr.setAttrName(baseAttrInfo.getAttrName());
                searchAttr.setAttrValue(baseAttrInfo.getAttrValue());
                return searchAttr;
            }).collect(Collectors.toList());
            goods.setAttrs(searchAttrList);
        }
        }, threadPoolExecutor);
        goods.setCreatedDate(new Date());
        goods.setCreateTime(new Date());

        CompletableFuture.allOf(
                skuInfoCompletableFuture,
                priceCompletableFuture,
                attrInfoCompletableFuture,
                categoryCompletableFuture).join();

        //6.调用JavaHighLevelRestClient完成文档新增(发起http请求)
        //6.1构建创建文档请求对象,InddexRequest(index_name).id(skuId.toString方法)
        IndexRequest request = new IndexRequest(INDEX_NAME).id(skuId.toString());
        //6.2新增构建文档请求参数 json
        request.source(JSON.toJSONString(goods), XContentType.JSON);
        //6.3发请求给es
        try {
            restHighLevelClient.index(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 处理下架请求(商品文件在索引库删除)
     * @param skuId
     * @return
     */
    @Override
    public void lowerGoods(Long skuId) {
        try {
            DeleteRequest request = new DeleteRequest(
                    INDEX_NAME,
                    skuId.toString());
            restHighLevelClient.delete(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("[搜索服务]下架商品:{},失败原因:{}", skuId, e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 跟新商品热度分值
     * @param skuId
     */
    @Override
    public void incrHotScore(Long skuId) {
        try {
            //1.跟新redis中缓存的值
            String key = "HotScore";
            Double skuHostScore = redisTemplate.opsForZSet().incrementScore(key, skuId.toString(),1);
            //2.跟新es中缓存的值
            if(skuHostScore % 10 == 0){
                //创建更新文档请求对象,文档id,文档分值
                UpdateRequest updateRequest = new UpdateRequest(INDEX_NAME, skuId.toString());
                Goods goods = new Goods();
                goods.setHotScore(skuHostScore.longValue());
                updateRequest.doc(JSON.toJSONString(goods),XContentType.JSON);
                try {
                    restHighLevelClient.update(updateRequest,RequestOptions.DEFAULT);
                } catch (IOException e) {
                    log.error("[搜索微服务]更新商品分值:{},异常信息:{}", skuId, e);
                    throw new RuntimeException(e);
                }
            }
        } catch (RuntimeException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 构建调用es,Http接口,请求信息(请求地址,方式,路径参数,请求体参数)
     * @param searchParam
     * @return
     */
    @Override
    public SearchResponseVo search(SearchParam searchParam) {
        try {
            //一.构建搜索请求对象(封装请求对象,请求方式,路径,请求参数)
            SearchRequest searchRequest = this.builderDSL(searchParam);
            //二.执行检索
            SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            //三.封装响应结果
            return this.parseResult(response, searchParam);
        } catch (Exception e) {
            log.error("[搜索服务]检索业务异常:{}",e,searchParam);
            throw new RuntimeException(e);
        }
    }


    /**
     * 构建查询请求对象,构建DSL语句中请求和请求参数
     * @param response
     * @return
     */
    private SearchRequest builderDSL(SearchParam searchParam) {
        //1.构建搜索请求对象,封装GET /goods/_search,请求参数,请求路径参数中索引库名称
        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);

        //2.构建请求体构建器对象,封装请求体JSON参数:查询,分页,高亮,排序.字段制定,聚合
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //2.1设置查询条件,请求参数中query部分
        //2.1.1构建所有封装布尔查询条件对象
        BoolQueryBuilder allBoolQueryBuilder = QueryBuilders.boolQuery();
        //2.1.2根据关键字检索,采用模糊/关键字 查询
        if(StringUtils.isNotBlank(searchParam.getKeyword())){
            allBoolQueryBuilder.must(QueryBuilders.matchQuery("title", searchParam.getKeyword()).operator(Operator.AND));
        }
        //2.1.3设置布尔查询条件中过滤 1:品牌 2:平台性属性
        //2.1.3.1设置品牌过滤
        if(StringUtils.isNotBlank(searchParam.getTrademark())){
            String[] split = searchParam.getTrademark().split(":");
            if(split != null && split.length == 2){
                allBoolQueryBuilder.filter(QueryBuilders.termQuery("tmId", split[0]));
            }
        }
        //2.1.3.2设置过滤分类
        if(searchParam.getCategory1Id() != null){
            allBoolQueryBuilder.filter(QueryBuilders.termQuery("category1Id", searchParam.getCategory1Id()));
        }
        if(searchParam.getCategory2Id() != null){
            allBoolQueryBuilder.filter(QueryBuilders.termQuery("category2Id", searchParam.getCategory2Id()));
        }
        if(searchParam.getCategory3Id() != null){
            allBoolQueryBuilder.filter(QueryBuilders.termQuery("category3Id", searchParam.getCategory3Id()));
        }
        //2.1.3.3设置平台属性过滤
        //构建封装所有平台属性布尔查询
        BoolQueryBuilder allAttrBoolQueryBuilder = QueryBuilders.boolQuery();
        if(searchParam.getProps() != null && searchParam.getProps().length > 0){
            //动态构建某个平台属性过滤
            for (String prop : searchParam.getProps()) {
                String[] split = prop.split(":");
                if(split != null && split.length == 3){
                    //每一次循环构建一个nested查询,每一组平台属性过滤条件,又是一个布尔查询
                    BoolQueryBuilder attrBoolQueryBuilder = QueryBuilders.boolQuery();
                    attrBoolQueryBuilder.must(QueryBuilders.termQuery("attrs.attrId", split[0]));
                    attrBoolQueryBuilder.must(QueryBuilders.termQuery("attrs.attrValue", split[1]));
                    NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", attrBoolQueryBuilder, ScoreMode.None);
                    allAttrBoolQueryBuilder.must(nestedQuery);

                }
            }
        }
        allAttrBoolQueryBuilder.filter(allBoolQueryBuilder);
        sourceBuilder.query(allBoolQueryBuilder);

        //2.2设置高亮
        if(StringUtils.isNotBlank(searchParam.getKeyword())){
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            //2.2.1设置前置标签
            highlightBuilder.preTags("<font style='color:red'>");
            //2.2.3设置后置标签
            highlightBuilder.postTags("</font>");
            //2.2.4设置高亮字段
            highlightBuilder.field("title");
            sourceBuilder.highlighter(highlightBuilder);
        }

        //2.3设置分页,请求参数"form"和"size"
        //2.3.1计算起始位置
        int from = (searchParam.getPageNo() -1 )* searchParam.getPageSize();
        sourceBuilder.from(from).size(searchParam.getPageSize());
        //2.3.2设置排序(前提是前端提交的参数sort,前提是前端提交的参数形式:2:asc升序,1:按照热度排 2:按照价格排序)
        if(StringUtils.isNotBlank(searchParam.getOrder())){
            String[] split = searchParam.getOrder().split(":");
            if(split != null && split.length == 2){
                String orderField = "";
                if(split[0].equals(1)){
                    //按照热度排序
                    orderField = "hotScore";
                }else if(split[0].equals(2)){
                    //按照价格排序
                    orderField = "price";
                }
                sourceBuilder.sort(orderField, "asc".equals(split[1]) ? SortOrder.ASC :SortOrder.DESC);
            }
        }

        //TODO:这段复制粘贴的,需要在写一遍
        //2.5 设置响应文档字段列表,请求参数 "_source"
        sourceBuilder.fetchSource(new String[]{"id", "title", "price", "defaultImg"}, null);
        //2.6 设置聚合,请求参数 "aggs"
        //2.6.1 设置品牌聚合
        //2.6.1.1 先创建品牌ID聚合对象 terms("聚合名称")
        TermsAggregationBuilder tmIdAggregation = AggregationBuilders.terms("tmIdAgg").field("tmId");
        //2.6.1.1 基于品牌ID聚合对象设置子聚合-品牌名称聚合
        tmIdAggregation.subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"));
        //2.6.1.2 基于品牌ID聚合对象设置子聚合-品牌Logo聚合
        tmIdAggregation.subAggregation(AggregationBuilders.terms("tmLogoAgg").field("tmLogoUrl"));
        sourceBuilder.aggregation(tmIdAggregation);

        //2.6.2 设置平台属性聚合
        //2.6.2.1 创建嵌套类型nested平台属性聚合对象
        NestedAggregationBuilder nestedAggregation = AggregationBuilders.nested("attrAgg", "attrs");
        //2.6.2.2 创建平台属性ID聚合对象
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attrIdAgg").field("attrs.attrId");
        //2.6.2.2.1 基于平台属性ID聚合对象创建平台属性名称聚合对象
        attrIdAgg.subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"));
        //2.6.2.2.2 基于平台属性ID聚合对象创建平台属性值聚合对象
        attrIdAgg.subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"));

        //2.6.2.3 将平台属性ID聚合对象加入到平台属性聚合对象中
        nestedAggregation.subAggregation(attrIdAgg);

     sourceBuilder.aggregation(nestedAggregation);
     //3.在搜索请求对象中添加请求体构造器对象
     System.err.println(sourceBuilder.toString());
     return searchRequest.source(sourceBuilder);
     }

     /**
     * 从es的封装响应对象中获取所需要的数据封装为自定义Vo对象
     * @param response
     * @return
     */
    private SearchResponseVo parseResult(SearchResponse response,SearchParam searchParam) {
        //构建响应Vo对象
        SearchResponseVo searchResponseVo = new SearchResponseVo();
        //1.封装响应Vo对象中分页信息
        searchResponseVo.setPageNo(searchResponseVo.getPageNo());
        Integer pageSize = searchParam.getPageSize();
        searchResponseVo.setPageSize(pageSize);
        long total = response.getHits().getTotalHits().value;
        searchResponseVo.setTotal(total);
        long totalPages = total % pageSize == 0 ? (total / pageSize) : (total / pageSize + 1);
        searchResponseVo.setTotalPages(totalPages);

        //2.封装响应Vo对象中当前业务数据
        SearchHit[] hits = response.getHits().getHits();
        if(hits != null && hits.length > 0){
            //遍历命中数据得到json.将json反序列化为java对象得到Goods
            List<SearchHit> searchHits = Arrays.asList(hits);
            List<Goods> goodsList = searchHits.stream().map(hit -> {
                //获取商品文档json数据
                String goodsJsonStr = hit.getSourceAsString();
                //转换为goods对象
                Goods goods = JSON.parseObject(goodsJsonStr, Goods.class);
                //获取高亮结果
                Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                if (!CollectionUtils.isEmpty(highlightFields)) {
                    //获取高亮结果对象
                    HighlightField highlightField = highlightFields.get("title");
                    if (highlightField != null) {
                        //获取高亮片段数组
                        Text[] fragments = highlightField.getFragments();
                        if (fragments != null && fragments.length > 0) {
                            //获取高亮内容
                            String titleHightLight = fragments[0].toString();
                            goods.setTitle(titleHightLight);
                        }
                    }
                }
                return goods;
            }).collect(Collectors.toList());
            searchResponseVo.setGoodsList(goodsList);
        }
        //3.封装响应Vo对象中用于展示过滤项,品牌聚合结果
        //3.1获取所有的聚合结果-Map
        Map<String, Aggregation> AllaggregationMap = response.getAggregations().asMap();
        //3.1获取品牌id聚合对象,将聚合结果接口转换为实现类
        ParsedLongTerms tmIdAgg = (ParsedLongTerms) AllaggregationMap.get("tmIdAgg");
        //3.2.1从品牌聚合对象中获取bucket数组
        List<? extends Terms.Bucket> buckets = tmIdAgg.getBuckets();
           if(!CollectionUtils.isEmpty(buckets)){
               List<SearchResponseTmVo> responseTmVos = buckets.stream().map(bucket -> {
                   SearchResponseTmVo searchResponseTmVo = new SearchResponseTmVo();
                   //遍历bucket数组,获取品牌bucket对象
                   //获取到聚合品牌id
                   long tmId = bucket.getKeyAsNumber().longValue();
                   //根据品牌id集合结果,获取品牌名称
                   //根据品牌聚合结果获取品牌logo
                   Map<String, Aggregation> tmNameAndLogoAggregation = bucket.getAggregations().asMap();
                   if (!CollectionUtils.isEmpty(tmNameAndLogoAggregation)) {
                       //获取品牌名称
                       ParsedStringTerms tmNameAgg = (ParsedStringTerms) tmNameAndLogoAggregation.get("tmNameAgg");
                       if(tmNameAgg!=null && !CollectionUtils.isEmpty(tmNameAgg.getBuckets())){
                           String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
                           searchResponseTmVo.setTmName(tmName);
                       }

                       //获取品牌logo

                       ParsedStringTerms tmLogoAgg = (ParsedStringTerms) tmNameAndLogoAggregation.get("tmLogoAgg");
                       if(tmLogoAgg!=null && !CollectionUtils.isEmpty(tmLogoAgg.getBuckets())){
                           String tmLogoUrl = tmLogoAgg.getBuckets().get(0).getKeyAsString();
                           searchResponseTmVo.setTmLogoUrl(tmLogoUrl);
                       }

                   }
                   searchResponseTmVo.setTmId(tmId);
                   return searchResponseTmVo;
               }).collect(Collectors.toList());
               searchResponseVo.setTrademarkList(responseTmVos);
           }

        //4.封装响应Vo对象中用于展示过滤项-->平台属性聚合结果
        //4.1获取平台属性聚合对象
        ParsedNested attrAgg = (ParsedNested) AllaggregationMap.get("attrAgg");
        //4.2根据平台属性聚合对象获取平台属性子聚合对象
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        //4.3根据平台属性聚合对象获取聚合结果bucket数组,遍历数组获取平台属性,名称,属性值
        List<? extends Terms.Bucket> attrIdAggBuckets = attrIdAgg.getBuckets();
        if(!CollectionUtils.isEmpty(attrIdAggBuckets)){
            List<SearchResponseAttrVo> attrVoList = attrIdAggBuckets.stream().map(bucket -> {
                SearchResponseAttrVo attrVo = new SearchResponseAttrVo();
                //4.3.1通过bucket对象获取聚合到平台属性id
                long attrId = bucket.getKeyAsNumber().longValue();
                attrVo.setAttrId(attrId);
                //4.3.2通过bucket获取平台属性名称子聚合对象
                ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attrNameAgg");
                if (!CollectionUtils.isEmpty(attrNameAgg.getBuckets())) {
                    String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
                    attrVo.setAttrName(attrName);
                }
                //4.3.3 通过bucket对象获取平台属性值子聚合对象
                ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attrValueAgg");
                List<? extends Terms.Bucket> valueAggBuckets = attrValueAgg.getBuckets();
                if (!CollectionUtils.isEmpty(valueAggBuckets)) {
                    //映射bucket集合,获取每个bucket转成字符串
                    List<String> attrValueList = valueAggBuckets
                            .stream().map(MultiBucketsAggregation
                            .Bucket::getKeyAsString).collect(Collectors.toList());
                    attrVo.setAttrValueList(attrValueList);
                }
                return attrVo;
            }).collect(Collectors.toList());
            searchResponseVo.setAttrsList(attrVoList);
        }
        return searchResponseVo;
    }
}